iT邦幫忙

2023 iThome 鐵人賽

DAY 29
1
Modern Web

深入淺出,完整認識 Next.js 13 !系列 第 29

Day 29 - Next.js 13 的快取機制 ( 二 ) - Full Route Cache & Router Cache

  • 分享至 

  • xImage
  •  

時間過得很快,也來到本系列文章的倒數第二篇。

昨天介紹了 Next 快取機制的前兩層 - Data Cache 和 Request Memoization。兩者主要會在 Server Components 進行 data fetching 時觸發。

快速複習一下兩者的目的:

  • Data Cache:減少向 data source 索取相同資料的次數
  • Request Memoization:避免重複處理相同 requests

假如還不認識兩者的讀者,建議先閱讀 Day 28 的文章。

昨天似乎成功透過禁止 Data Cache 和 Request Memoization,解決文章開頭,每頁用戶代碼一樣的問題。

但假如我現在做個小改動:一樣禁用 Data Cache,可是把 <a> 改成 <Link>,來看看改成 soft navigation 後,會不會發生什麼改變:

不要懷疑,你沒有眼花,也不是重複播放的關係,三個頁面的用戶代碼的確不一樣,但第二輪和第三輪拜訪,用戶代碼卻還是跟第一輪一樣。所以影片就一直在 213 -> 243 -> 241 循環。

明明禁用 Data Cache 了,為什麼會這樣呢?我們接著往快取層上層找,看看到底是誰在搞鬼吧!


Full Route Cache

顧名思義,Full Route Cache 就是快取整個 route segment。在 build time,Next 會先渲染靜態路由,並快取渲染結果,避免用戶每次拜訪頁面時,都要執行 server-side rendering,加速頁面載入速度。類似我們 Day 03 提到的 ISR 作法。

所以當 server 接到要渲染 route segment 的 request 時,會先檢查快取有沒有這個 route segment 渲染結果 ( HTML & RSC Payload)。

假如不知道什麼是 RSC Payload,可參考 Day 12 文章

假如有,就直接回覆快取內容;假如沒有,就會進行渲染。渲染時假如有 fetch requests,就會檢查有沒有 Reqeust Memoization,沒有才進行 data fetching。進行 data fetching 時,會檢查有沒有 Data Cache,沒有才向 data source 拿資料。

這樣就將 Full Route Cache 和昨天的內容串起來啦!提供官方文件的 infographic 給大家參考:

( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache )

禁用 Full Route Cache
上述有提到,Next 只會快取靜態渲染的內容,所以只要是動態渲染就不會觸發 Full Route Cache。

動態渲染的意思是,需要在 run-time 根據 request 決定渲染內容。像是使用 cookies、searchParams 等功能 ( dynamic function ),Next 就會自動轉成動態渲染,route segment 就不會在 build-time 渲染。

假如是動態渲染,server 收到渲染 request 時會略過 Full Route Cache 的檢查,直接進行渲染。


( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache )

要觸發動態渲染,除了使用 dynamic functions 外,還有幾個方法:

  1. 調整 route segment 設定
    可以使用 revalidate = 0dynamic = 'force-dynamic' 來強迫路由動態渲染:

    export const dynamic = 'force-dynamic';
    export const revalidate = 0;
    
    export default async function Page() {
      return (
        ...
      );
    }
    
    

    使用兩者,Full Route Cache 和 Data Cache 都不會觸發。

    還有其他 route segment 設定可以使用,像是 Day 19 提到的 dynamicParams。有興趣的讀者可參考官方文件

  2. 禁用 Data Cache
    假如某個 fetch request 使用昨天提到的 cache='no-store' 禁用 Data Cache,也會連帶禁用 Full Route Cache。

    禁用某個 fetch request 的 Data Cache 並不會影響其他 fetch request 的 Data Cache。 一樣做個小實驗:

    我們在 /users fetch 兩次 /api/hello ,並讓其中一個 request 帶 {cache: 'no-store'},接著渲染這兩個 response:

     /* app/users/page.tsx */
    const fetchUserId = async (url: string, option?: RequestInit) => {
      const res = await fetch(url, option);
      const jsonData = await res.json();
      return jsonData.message;
    };
    
    export default async function Page() {
      const url = 'http://localhost:3000/api/hello';
      const no_cache_option = { cache: 'no-store' };
    
      const userId_cache = await fetchUserId(url);
      const userId_no_cache = await fetchUserId(
        url,
        no_cache_option as RequestInit
      );
      return (
        <div className='...'>
          <h1>{userId_cache}</h1>
          <h1>{userId_no_cache}</h1>
        </div>
      );
    }
    
    

    完成後,我們重新整理幾次頁面,看看會發生什麼事:

    會發現,只有第二個用戶代碼隨著重新整理變化。所以雖然第二個 request 禁用 Data Cache,Full Route Cache 也隨之禁用,但並不影響第一個 request 的 Data Cache。

重置 Full Route Cache
當 Data Cache 重置時,也會清除 Full Route Cach;除此之外,重新部署也會清除 Full Route Cache。

Data Cache 除非有設定重置條件或主動重置,不然會永久存在。就算重新部署也不會清除。

回到開頭的問題,很顯然問題也不在 Full Route Cache,因為禁用 Data Cache 就不會觸發 Full Route Cache。

所以只剩最後一個選項 - Router Cache。在開始進一步調查之前,先來快速認識 Router Cache:

Router Cache

Router Cache 是 Next 在瀏覽器的 in-memory cache,專門負責存 route segment 的 RSC Payload。

當用戶在做 soft navigation 時,Next 渲染完目前 route segment 的內容,會快取 RSC Payload,以加快重複拜訪時,頁面載入的速度。

也適用 Prefetched 的路由
Day 20 有提到,<Link> 預設會提前載入目的地的資源。假如沒有禁用 prefetch,prefetched 路由的 RSC Payload 也會快取。

( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#router-cache )

失效條件

  1. 用戶重新整理頁面,會清除所有 Router Cache
  2. 使用 revalidatePathrevalidateTag ( 可參考昨天文章 )
  3. 使用 router.refresh ( 可參考昨天文章 )
  4. Route Handler 或 Server Acions 中使用 cookies.setcookies.delete
  5. 到達自動失效時間:靜態渲染為 30 秒,動態渲染為 5 分鐘,時間到會清除該 route segment 的 Router Cache

補充一下,雖然官方文件說 Route Handler 可以使用官方 API 的 cookies.set function,但根據 issue#52799 官方的說法,cookies.set 無法在 rendering 時使用。

意思是,假如要在 Route Handler 使用 cookies.set,要直接在瀏覽器拜訪 API 連結,或用 postman 打這支 API 才有效,小弟實測也確實如此,還蠻玄的...

所以假如要使用 cookies.set,官方建議在 middleware 或 Server Actions 中使用。

快速認識 Router Cache 後,我們打開 DevTools 的 Network,觀察一下路由切換時的是否有向 server 取 RSC Payload:

可以觀察到,當我們進到第一次 /welcome、/dashbaord、/shop 時,會向 server 取對應的 RSC Payload,但後續再次拜訪,就不會再取一次。

假如仔細觀察上方的影片,會發現,用戶代碼也是第二輪開始停止更新,與停止載入新的 RSC Payload 的時機一樣。

假如回顧昨天的文章,的確使用 <a> 製造 hard navigation,或使用 revalidatePathrouter.refresh 都可以讓用戶代碼更新。看樣子開頭的問題,確實是 Router Cache 造成的。

那我們有辦法直接禁用它嗎?

禁用 Router Cache
我們來看官方怎麼說:

It's not possible to opt out of the Router Cache.

很遺憾地,我們無法禁用 Router Cache。所以假如希望頁面切換時,能自動清除 Router Cache,目前只能捨棄 <Link> 改用原生的 <a>

四種快取機制回顧

認識完四個快取機制後,最後我們再回顧一次昨天前言的圖:

( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching )

簡單來說,當使用者進到頁面,假如有 Server Components:

  1. 檢查有沒有 Router Cache,有就用 Router Cache 中的 RSC Payload 渲染畫面;沒有的話,則請求 Server 渲染。
  2. Server 收到請求後,先檢查有沒有 Full Route Cache,有就直接回覆快取的渲染結果;沒有則進行渲染。
  3. 渲染過程假如有 fetch request,先檢查有沒有 Request Memoization,有的話直接回覆快取的 response;沒有則執行 data fetching。
  4. 執行 data fetching 時,先檢查有沒有 Data Cache,有則直接回覆快取的資料;沒有則到 data source 撈資料。

以上,就是 Next.js 13 快取機制的介紹,Next.js 13 的介紹也到這邊告一段落。希望有幫助到大家,在開發 Next 專案時,工具技術的選擇上,能多一些參考依據,以及 debug 時,能多一些線索。

大致認識完 Next.js 13 的基本功能後,下一步呢?這部分就留到最後一天和大家分享囉。

謝謝大家耐心的閱讀,我們明天見!


上一篇
Day 28 - Next.js 13 的快取機制 ( 一 ) - Data Cache & Request Memoization
下一篇
Day 30 - 學習的下一步 & 完賽感言
系列文
深入淺出,完整認識 Next.js 13 !30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言